﻿Imports System.Reflection

''' <summary>
''' Contains the collection of command methods currently registered to the interpreter, and allows registration of new command methods or removal of existing ones.
''' </summary>
''' <remarks></remarks>
Public Class CommandMethodCollection
    Implements IEnumerable(Of RegisteredCommandMethod)

    Private _ListCore As New List(Of RegisteredCommandMethod)
    Private _NameKeys As New List(Of String)

    Protected _AlternateCommandNames As New Dictionary(Of String, String)
    Public ReadOnly Property AlternateCommandNames As Dictionary(Of String, String)
        Get
            Return _AlternateCommandNames
        End Get
    End Property

    Default Public ReadOnly Property Item(index As Integer) As RegisteredCommandMethod
        Get
            Return _ListCore(index)
        End Get
    End Property

    Default Public ReadOnly Property Item(name As String) As RegisteredCommandMethod
        Get
            Return _ListCore(_NameKeys.IndexOf(name))
        End Get
    End Property

    Protected Friend Sub Add(item As RegisteredCommandMethod)
        _ListCore.Add(item)
        _NameKeys.Add(item.CommandName)
    End Sub

    Protected Friend Function AddNew(commandAttribute As CommandMethodAttribute, info As MethodInfo, target As Object, argumentAttributes() As CommandMethodArgumentAttribute) As RegisteredCommandMethod
        Dim result As RegisteredCommandMethod = Nothing
        Array.Sort(argumentAttributes)
        Dim testName As String = info.Name
        If Not CommandInterpreter.CaseSensitive Then
            testName = testName.ToLower
        End If
        If Contains(testName) Then
            result = New RegisteredCommandMethod(commandAttribute, info, target, True, argumentAttributes)
            If Not Contains(result.CommandName) Then
                Add(result)
            Else
                result = Nothing
            End If
        Else
            result = New RegisteredCommandMethod(commandAttribute, info, target, False, argumentAttributes)
            Add(result)
        End If
        For Each alternateName As String In commandAttribute.AlternateNames
            _AlternateCommandNames(alternateName.ToLower) = result.CommandName
        Next
        Return result
    End Function

    Public Sub Clear()
        _ListCore.Clear()
        _NameKeys.Clear()
    End Sub

    Public Function Contains(name As String) As Boolean
        Return _NameKeys.Contains(name)
    End Function

    Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of RegisteredCommandMethod) Implements System.Collections.Generic.IEnumerable(Of RegisteredCommandMethod).GetEnumerator
        Return _ListCore.GetEnumerator
    End Function

    Protected Function GetEnumeratorBase() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
        Return GetEnumerator()
    End Function

    ''' <summary>
    ''' Registers all command methods marked with CommandMethod attribute found on the target object.
    ''' </summary>
    ''' <param name="target">The object instance containing methods marked with the CommandMethod attribute.</param>
    ''' <remarks></remarks>
    Public Sub RegisterObject(target As Object)
        Dim objectType As Type = target.GetType
        Dim methods() As MethodInfo = objectType.GetMethods(BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public Or BindingFlags.Static)
        For Each info As MethodInfo In methods
            Dim attribs() As Attribute = info.GetCustomAttributes(GetType(CommandMethodAttribute), True)
            If attribs.Length = 1 Then
                Dim argAttribs() As CommandMethodArgumentAttribute = info.GetCustomAttributes(GetType(CommandMethodArgumentAttribute), True)
                AddNew(attribs(0), info, target, argAttribs)
            End If
        Next
    End Sub

    Public Sub RegisterMethod(method As Action(Of CommandLine), ParamArray argumentAttributes() As CommandMethodArgumentAttribute)
        Dim testAttributes() As Attribute = method.Method.GetCustomAttributes(GetType(CommandMethodAttribute), True)
        If testAttributes.Length > 0 Then
            RegisterMethod(method, testAttributes(0), argumentAttributes)
        Else
            RegisterMethod(method, String.Empty, String.Empty, False, argumentAttributes)
        End If
    End Sub

    Public Sub RegisterMethod(method As Action(Of CommandLine), description As String, ParamArray argumentAttributes() As CommandMethodArgumentAttribute)
        RegisterMethod(method, description, String.Empty, False, argumentAttributes)
    End Sub

    Public Sub RegisterMethod(method As Action(Of CommandLine), description As String, formattedHelp As String, ParamArray argumentAttributes() As CommandMethodArgumentAttribute)
        RegisterMethod(method, description, formattedHelp, False, argumentAttributes)
    End Sub

    Public Sub RegisterMethod(method As Action(Of CommandLine), hidden As Boolean, ParamArray argumentAttributes() As CommandMethodArgumentAttribute)
        RegisterMethod(method, String.Empty, String.Empty, hidden, argumentAttributes)
    End Sub

    Public Sub RegisterMethod(method As Action(Of CommandLine), description As String, formattedHelp As String, hidden As Boolean, ParamArray argumentAttributes() As CommandMethodArgumentAttribute)
        Dim attr As New CommandMethodAttribute(description, formattedHelp, hidden)
        RegisterMethod(method, attr, argumentAttributes)
    End Sub

    Public Sub RegisterMethod(method As Action(Of CommandLine), attribute As CommandMethodAttribute, ParamArray argumentAttributes() As CommandMethodArgumentAttribute)
        Dim target As Object = method.Target
        If target Is Nothing Then target = method.Method.Module
        Dim argAttribs As New List(Of CommandMethodArgumentAttribute)(argumentAttributes)
        argAttribs.AddRange(method.Method.GetCustomAttributes(GetType(CommandMethodArgumentAttribute), True))
        AddNew(attribute, method.Method, target, argAttribs.ToArray)
    End Sub

    Public Function Remove(commandName As String) As Boolean
        Dim index As Integer = _NameKeys.IndexOf(commandName)
        If index > -1 Then
            RemoveAt(index)
            Return True
        End If
        Return False
    End Function

    Public Sub RemoveAt(index As Integer)
        _ListCore.RemoveAt(index)
        _NameKeys.RemoveAt(index)
    End Sub

    ''' <summary>
    ''' Removes all command methods which were originally registered using the target object.
    ''' </summary>
    ''' <param name="target"></param>
    ''' <remarks></remarks>
    Public Sub RemoveObjectRegistrations(target As Object)
        For index As Integer = Count - 1 To 0 Step -1
            If Item(index).Target Is target Then
                RemoveAt(index)
            End If
        Next
    End Sub

End Class
